package tech

import (
	"ssp/set"
)

func (g *Grid) getSKLoopCells(rowNum, colNum int) (rowCells, colCells []*Cell) {
	boxNum, _ := calcBoxAndCellNum(rowNum, colNum)
	box := g.boxes[boxNum]
	rowStart := box.cells[0].rowNum
	colStart := box.cells[0].colNum

	cellNum1 := (rowNum-rowStart)*3 + ((colNum-colStart)+1)%3
	cellNum2 := (rowNum-rowStart)*3 + ((colNum-colStart)+2)%3
	for _, cell := range []*Cell{box.cells[cellNum1], box.cells[cellNum2]} {
		if !cell.solved() {
			rowCells = append(rowCells, cell)
		}
	}

	cellNum1 = (((rowNum-rowStart)+1)%3)*3 + (colNum - colStart)
	cellNum2 = (((rowNum-rowStart)+2)%3)*3 + (colNum - colStart)
	for _, cell := range []*Cell{box.cells[cellNum1], box.cells[cellNum2]} {
		if !cell.solved() {
			colCells = append(colCells, cell)
		}
	}
	return
}

func (g *Grid) getSKLBoxCandsLines(rowNum, colNum int) (chvCands [][3]set.Set) {
	rowCells, colCells := g.getSKLoopCells(rowNum, colNum)
	rowCands := unionCellCands(rowCells)
	colCands := unionCellCands(colCells)
	if len(rowCells) == 1 && len(colCells) == 1 {
		return
	}
	//fmt.Println("1. rowNum, colNum", rowNum, colNum, rowCands, colCands)
	if (len(rowCells) == 2 && len(colCells) == 2) && ((rowCands.Count() != 3 && rowCands.Count() != 4 && rowCands.Count() != 5) || (colCands.Count() != 3 && colCands.Count() != 4 && colCands.Count() != 5)) {
		return
	}
	//fmt.Println("2. rowNum, colNum", rowNum, colNum, rowCands, colCands)
	if (len(rowCells) == 1 || len(colCells) == 1) && (rowCands.Count() != 3 || colCands.Count() != 3) {
		return
	}
	//fmt.Println("3. rowNum, colNum", rowNum, colNum, rowCands, colCands)

	sharedCands := rowCands.Intersect(colCands)
	for i := 1; i <= min(rowCands.Count(), colCands.Count(), sharedCands.Count()+1); i++ {
		for _, cands := range combine(sharedCands.GetList(), i) {
			ccands := set.NewSet(cands...)
			hcands := rowCands.Diff(ccands)
			vcands := colCands.Diff(ccands)
			if ccands.Count() == 0 || ccands.Count() > 3 || hcands.Count() == 0 || hcands.Count() > 3 || vcands.Count() == 0 || vcands.Count() > 3 {
				continue
			}
			chvCands = append(chvCands, [3]set.Set{ccands, hcands, vcands})
		}
	}

	return
}

/*
func (g *Grid) skLoopBoxCands(box House) map[[2]int][][3]set.Set {
	rcChvCands := make(map[[2]int][][3]set.Set, 0)
	startRow := box.cells[0].rowNum
	startCol := box.cells[0].colNum
	for rowNum := startRow; rowNum < startRow+3; rowNum++ {
		for colNum := startCol; colNum < startCol+3; colNum++ {
			chvCands := g.getSKLBoxCandsLines(rowNum, colNum)
			if len(chvCands) > 0 {
				rcChvCands[[2]int{rowNum, colNum}] = chvCands
			}
		}
	}
	return rcChvCands
}
*/
/*
func (s *Solve) SKLoop(g *Grid) (used bool, results []*Result) {
	boxNumss := [][]int{{0, 1, 3, 4}, {0, 1, 6, 7}, {0, 2, 3, 5}, {0, 2, 6, 8},
		{1, 2, 4, 5}, {1, 2, 7, 8}, {3, 4, 6, 7}, {3, 5, 6, 8}, {4, 5, 7, 8}}
	for _, boxNums := range boxNumss {
		if ok, rs := g.skLoopBoxes(boxNums); ok {
			used = true
			results = append(results, rs...)
		}
	}
	return
}
*/

func (g *Grid) findSKLoop() (chvCandss0123 map[[4]int][][4][3]set.Set) {
	chvCandss0123 = make(map[[4]int][][4][3]set.Set, 0)
	cache := make(map[[2]int][][3]set.Set, 0)
	for row0 := 0; row0 < 6; row0++ {
		for col0 := 0; col0 < 6; col0++ {
			//fmt.Println("2 row0, col0", row0, col0)
			chvCandss0, ok0 := cache[[2]int{row0, col0}]
			if !ok0 {
				chvCandss0 = g.getSKLBoxCandsLines(row0, col0)
				cache[[2]int{row0, col0}] = chvCandss0
			}
			if len(chvCandss0) == 0 {
				continue
			}
			//fmt.Println("row0, col0", row0, col0, chvCandss0)
			for col1 := col0/3*3 + 3; col1 < 9; col1++ {
				chvCandss1, ok1 := cache[[2]int{row0, col1}]
				if !ok1 {
					chvCandss1 = g.getSKLBoxCandsLines(row0, col1)
					cache[[2]int{row0, col1}] = chvCandss1
				}
				//fmt.Println("1 row0, col1", row0, col1, chvCandss1)
				if len(chvCandss1) == 0 {
					continue
				}
				//fmt.Println("2 row0, col1", row0, col1, chvCandss1)
				chvCandss01 := hMatchChvCands(chvCandss0, chvCandss1)
				if len(chvCandss01) == 0 {
					continue
				}
				for row1 := row0/3*3 + 3; row1 < 9; row1++ {
					chvCandss2, ok2 := cache[[2]int{row1, col0}]
					if !ok2 {
						chvCandss2 = g.getSKLBoxCandsLines(row1, col0)
						cache[[2]int{row1, col0}] = chvCandss2
					}
					if len(chvCandss2) == 0 {
						continue
					}
					chvCandss02 := vMatchChvCands(chvCandss0, chvCandss2)
					if len(chvCandss02) == 0 {
						continue
					}
					chvCandss012 := [][3][3]set.Set{}
					for _, chvCands01 := range chvCandss01 {
						for _, chvCands02 := range chvCandss02 {
							if chvCands01[0] == chvCands02[0] {
								chvCandss012 = append(chvCandss012, [3][3]set.Set{
									chvCands01[0], chvCands01[1], chvCands02[1]})
							}
						}
					}
					if len(chvCandss012) == 0 {
						continue
					}
					chvCandss3, ok3 := cache[[2]int{row1, col1}]
					if !ok3 {
						chvCandss3 = g.getSKLBoxCandsLines(row1, col1)
						cache[[2]int{row1, col1}] = chvCandss3
					}
					if len(chvCandss3) == 0 {
						continue
					}

					for _, chvCands012 := range chvCandss012 {
						for _, chvCands3 := range chvCandss3 {
							if chvCands012[1][2] == chvCands3[2] && chvCands012[2][1] == chvCands3[1] {
								chvCandss0123[[4]int{row0, row1, col0, col1}] = append(
									chvCandss0123[[4]int{row0, row1, col0, col1}],
									[4][3]set.Set{chvCands012[0], chvCands012[1], chvCands012[2], chvCands3})
							}
						}
					}
				}
			}
		}
	}
	return
}

func (s *Solve) SKLoop(g *Grid) (used bool, results []*Result) {
	chvCandss0123 := g.findSKLoop()

	for k, v := range chvCandss0123 {
		hvCells := [4][2][]*Cell{}
		row0, row1, col0, col1 := k[0], k[1], k[2], k[3]
		hvCells[0][0], hvCells[0][1] = g.getSKLoopCells(row0, col0)
		hvCells[1][0], hvCells[1][1] = g.getSKLoopCells(row0, col1)
		hvCells[2][0], hvCells[2][1] = g.getSKLoopCells(row1, col0)
		hvCells[3][0], hvCells[3][1] = g.getSKLoopCells(row1, col1)
		for _, chvCands0123 := range v {
			type removed struct {
				cands   set.Set
				rmCells []*Cell
			}
			allRemoved := []removed{}
			for i := range 4 {
				cands := chvCands0123[i][0] //.Union(chvCands0123[i][1].Intersect(chvCands0123[i][2]))
				if ok, rmCells := canRemoveInHouse(
					cands, g.boxes[hvCells[i][0][0].boxNum],
					append(hvCells[i][0], hvCells[i][1]...)); ok {
					allRemoved = append(allRemoved, removed{cands, rmCells})
				}
			}
			if ok, rmCells := canRemoveInHouse(
				chvCands0123[0][1], g.rows[row0], append(hvCells[0][0], hvCells[1][0]...)); ok {
				allRemoved = append(allRemoved, removed{chvCands0123[0][1], rmCells})
			}
			if ok, rmCells := canRemoveInHouse(
				chvCands0123[2][1], g.rows[row1], append(hvCells[2][0], hvCells[3][0]...)); ok {
				allRemoved = append(allRemoved, removed{chvCands0123[2][1], rmCells})

			}
			if ok, rmCells := canRemoveInHouse(
				chvCands0123[0][2], g.cols[col0], append(hvCells[0][1], hvCells[2][1]...)); ok {
				allRemoved = append(allRemoved, removed{chvCands0123[0][2], rmCells})
			}
			if ok, rmCells := canRemoveInHouse(
				chvCands0123[1][2], g.cols[col1], append(hvCells[1][1], hvCells[3][1]...)); ok {
				allRemoved = append(allRemoved, removed{chvCands0123[1][2], rmCells})
			}

			if len(allRemoved) == 0 {
				continue
			}
			used = true
			if OUTPUTRESULT {
				result := new(Result)
				for _, removed := range allRemoved {
					result.addRemoved(removed.cands, removed.rmCells)
				}

				g1 := result.getHelpCandsGroup()
				g2 := result.getHelpCandsGroup()
				g3 := result.getHelpCandsGroup()
				for i := range 4 {
					g1.add(chvCands0123[i][0], hvCells[i][0])
					g1.add(chvCands0123[i][0], hvCells[i][1])
					g2.add(chvCands0123[i][1], hvCells[i][0])
					g3.add(chvCands0123[i][2], hvCells[i][1])
				}
				allhCells := []*Cell{}
				allvCells := []*Cell{}
				for i := range 4 {
					allhCells = append(allhCells, hvCells[i][0]...)
					allvCells = append(allvCells, hvCells[i][1]...)
				}
				result.addHelpCells(allhCells)
				result.addHelpCells(allvCells)
				result.setStrategyName("SK Loop")
				results = append(results, result)
			}
			if CHANGE {
				for _, removed := range allRemoved {
					removeCands(removed.cands, removed.rmCells)
				}
			}

			if !EXHAUSTIVE {
				return
			}
		}
	}
	return
}

func canRemoveInHouse(cands set.Set, house House, butCells []*Cell) (ok bool, rmCells []*Cell) {
	for _, cell := range house.cells {
		if cell.solved() || cell.isMemberOf(butCells) {
			continue
		}
		if cell.canRemoveCands(cands) {
			ok = true
			rmCells = append(rmCells, cell)
		}
	}
	return
}

func hMatchChvCands(chvCandss0, chvCandss1 [][3]set.Set) (chvCandss [][2][3]set.Set) {
	for _, chvCands0 := range chvCandss0 {
		for _, chvCands1 := range chvCandss1 {
			if chvCands0[1] == chvCands1[1] {
				chvCandss = append(chvCandss, [2][3]set.Set{chvCands0, chvCands1})
			}
		}
	}
	return
}

func vMatchChvCands(chvCandss0, chvCandss1 [][3]set.Set) (chvCandss [][2][3]set.Set) {
	for _, chvCands0 := range chvCandss0 {
		for _, chvCands1 := range chvCandss1 {
			if chvCands0[2] == chvCands1[2] && !chvCands0[2].IsNull() {
				chvCandss = append(chvCandss, [2][3]set.Set{chvCands0, chvCands1})
			}
		}
	}
	return
}
